﻿using System;
using System.Collections.Generic;
using System.Text;
using Simulator;
using Statistics;
using DatabaseRW;
using ModelConfigurator;
using System.Windows.Forms;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using DiagramParser;

namespace SimulationKernel
{
    /// <summary>
    /// Самый главный класс программы, координирующий работу всех модулей логики и связи с БД
    /// </summary>
    public class SimKernel
    {
        ///<summary>
        ///имя ядра симуляции
        /// </summary>
        public string Name;
        /// <summary>
        /// Список для операций отката
        /// </summary>
        private List<List<Block>> mementoList = new List<List<Block>>();

        private int currentState = 0;


        private SimModel sim;
        /// <summary>
        /// Установить или получить имитационную модель
        /// </summary>
        public SimModel Sim
        {
            get
            {
                return sim;
            }
            set
            {
                sim = value;
                if (sim.BlockList.Count > 0)
                {
                    this.blockList = sim.BlockList;
                }
            }
        }

        /// <summary>
        /// Оптимизатор текущей модели
        /// </summary>
        private SimOptimizer opt;

        
        /// <summary>
        /// Делегат для обновления текста на диаграмме
        /// </summary>
        /// <param name="Text"></param>
        public delegate void UpdateText(string blockName, string Text);
        /// <summary>
        /// Указатель на функцию для обновления текста (названия блоков) на диаграмме
        /// </summary>
        public UpdateText UpdateDiagramText;
        /// <summary>
        /// Список всех блоков модели
        /// </summary>
        public List<Block> blockList = new List<Block>();


        ///**************** СВОЙСТВА, ЗАГРУЖАЕМЫЕ ИЗ БАЗЫ ДАННЫХ *******************

        /// <summary>
        /// Список данных о процедурах, загруженных из БД
        /// </summary>
        private List<ActionData> actionsFromDB = new List<ActionData>();

        /// <summary>
        /// Список категорий атомарных ресурсов из БД, хранящий для каждой категории список атомарных ресурсов
        /// </summary>
        private SortedList<string, List<AtomicRes>> categories = new SortedList<string, List<AtomicRes>>();

        /// <summary>
        /// Список названий ВСЕХ параметров, загруженный из БД
        /// </summary>
        private List<string> paramNames = new List<string>();

        ///**************** КОНЕЦ СВОЙСТВ ИЗ БД **************************************
        /// <summary>
        /// Конструктор
        /// </summary>
        public SimKernel()
        {
            Sim = new SimModel();
            opt = new SimOptimizer();
            Sim.NCoefs = 5;
            Sim.NParams = 10;
            opt.StartModel = Sim;
            //настроим начальные свойства
            paramNames.Add("Стоимость покупки");
            paramNames.Add("Стоимость в месяц");
            for (int i = 0; i < Sim.NParams - 2; i++)
            {
                paramNames.Add("Параметр" + i);
            }
           // Sim.
        }
       
        /// <summary>
        /// Загрузить данные из базы данных
        /// </summary>
        /// <param name="ServerName">Имя сервера</param>
        /// <param name="DatabaseName">Название базы данных</param>
        /// <returns>ИСТИНА, если успешно загружено, иначе ЛОЖЬ</returns>
        public bool LoadFromDB(DBRW db)
        {
            //ДОБАВИТЬ КОД СЮДА
            paramNames.Clear(); categories.Clear(); actionsFromDB.Clear();
            ///******************* EXAMPLE ********
            /*
            ActionData ad = new ActionData();
            ad.Name = "Ввод";
            ad.QualityString = "5";
            ad.TimeString = "rndN(4;1)";
            ad.SimulationTimeUnits = TimeUnits.days;

            actionsFromDB.Add(ad);
            */
            //************************************
            return db.ReadDataBase(ref paramNames, ref categories, ref actionsFromDB);
        }
        /// <summary>
        /// Возвращает список имен блоков
        /// </summary>
        /// <returns></returns>
        public string[] GetBlockNames()
        {
            string[] names = new string[blockList.Count];
            for (int i = 0; i < blockList.Count; i++)
                names[i] = blockList[i].Name;
            return names;
        }
        /// <summary>
        /// Создать блок диаграммы
        /// </summary>
        public void CreateBlock(string Type, string Name, string Text)
        {
            //создаем блок
            Block b = DiagramToKernelParser.TranslateBlockType(Type);
            if (b != null) //если некоторые блоки не создают логические сущности
            {
                b.Name = Name;
                b.Text = Text;
                //настроим особые свойства для каждого блока
                switch(b.GetType().Name){
                    case "Action":
                        Action a = b as Action;
                        for(int i=0; i < paramNames.Count; i++){
                            a.WeightsP.Add(0);//добавим вес параметру, равный нулю
                        }
                        break;

                }
                //добавляем его в список
                blockList.Add(b);
            }
        }
        /// <summary>
        /// Вызвать окно настройки свойств блока
        /// </summary>
        /// <param name="Name">Имя блока</param>
        public void SetProperties(string Name)
        {
            //найдем блок по имени
            Block b = blockList.Find(delegate(Block block) { return block.Name == Name; });
            if (b == null)
                return;
            //смотрим, что за тип блока, и вызваем соотв. окна
            switch (b.GetType().Name)
            {
                case "Action":
                    FormAction f = new FormAction();
                    f.ActionsFromDB = actionsFromDB;//List<ActionData>, загруженный из БД
                    f.Edited = b as Action;//редактируемый блок типа Action
                    f.ParamNames = paramNames;//List<string> имена параметров ресурсов, общие для всей модели
                    if (f.ShowDialog() == DialogResult.OK)
                    {
                        //если свойства настроили и нажали ОК
                        b = f.Edited.Clone();
                        UpdateDiagramText(b.Name, b.Text);
                    }
                    break;
                case "Resource":
                    FormResource fr = new FormResource();
                    fr.Edited = b as Resource;
                    fr.ParamNames = paramNames;
                    fr.Categories = categories;
                    //если свойства настроили и нажали ОК
                    if (fr.ShowDialog() == DialogResult.OK)
                    {
                        b = fr.Edited.Clone();
                        UpdateDiagramText(b.Name, b.Text);
                    }
                    break;
                case "Decision":
                    FormDecision fd = new FormDecision();
                    fd.Edited = b as Decision;
                    //если свойства настроили и нажали ОК
                    if (fd.ShowDialog() == DialogResult.OK)
                    {
                        b = fd.Edited.Clone();
                        //UpdateDiagramText(b.Text);
                    }
                    break;
                case "Source":
                    FormSource fs = new FormSource();
                    fs.Edited = b as Source;
                    //если свойства настроили и нажали ОК
                    if (fs.ShowDialog() == DialogResult.OK)
                    {
                        b = fs.Edited.Clone();
                        //UpdateDiagramText(b.Text);
                    }
                    break;
                default:
                    MessageBox.Show("У этого блока нет специальных свойств", "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    break;
            }
        }
        /// <summary>
        /// Создать направленную связь между блоками
        /// </summary>
        /// <param name="NameFrom">Имя блока, из которого связь исходит</param>
        /// <param name="NameTo">Имя блока, в который связь входит</param>
        /// <param name="param">Дополнительный параметр линии связи</param>
        public void ConnectBlocks(string NameFrom, string NameTo, int param)
        {
            //найдем блоки по имени
            Block bfrom = blockList.Find(delegate(Block block) { return block.Name == NameFrom; });
            if (bfrom == null)
                return;
            Block bto = blockList.Find(delegate(Block block) { return block.Name == NameTo; });
            if (bto == null)
                return;
            //создадим направленные связи
            //если подключаем ресурс к процедуре: тип связи - ресурсы
            if ((bto.GetType().Name == "Action") && (bfrom.GetType().Name == "Resource"))
                (bto as Action).ConnectResource(bfrom as Resource);
            else //тип связи - сущности
            {
                //обработаем направление: prev -> next
                if (bfrom is Source || bfrom is Simulator.Action || bfrom is Collector || bfrom is Sync)
                    bfrom.Next = bto;
                
                //Если блок From - решение, то параметр определяет направление порта
                if (bfrom is Decision)
                {
                    (bfrom as Decision).Next[param] = bto;
                    //ремап: если по факту подключен неактивный выход, то делаем именно его активным false
                    if ((bfrom as Decision).PortMap[param] == 'h')
                    {
                        (bfrom as Decision).PortMap[
                            (bfrom as Decision).PortMap.FindIndex(p => p == 'f')] = 'h';
                        (bfrom as Decision).PortMap[param] = 'f';
                    }
                }
                //если блок From - параллельность
                if (bfrom is Parall)
                {
                    if ((bfrom as Parall).Next == null)
                        (bfrom as Parall).Next = bto;
                    else
                        (bfrom as Parall).Next2 = bto;
                }
                //направление next -> prev
                if (bto is Sink || bto is Simulator.Action || bto is Decision || bto is Parall)
                    bto.Prev = bfrom;
                if (bto is Collector)
                {
                    for (int i = 0; i < 3; i++)
                        if ((bto as Collector).Prev[i] == null)
                        {
                            (bto as Collector).Prev[i] = bfrom;
                            break;
                        }
                }
                if (bto is Sync)
                {
                    if ((bto as Sync).Prev == null)
                        (bto as Sync).Prev = bfrom;
                    else
                        (bto as Sync).Prev2 = bfrom;
                }
            }
        }
        /// <summary>
        /// Разорвать связть между двумя блоками
        /// </summary>
        public void DisconnectBlocks(string NameFrom, string NameTo)
        {
            //найдем блоки по имени
            Block bfrom = blockList.Find(delegate(Block block) { return block.Name == NameFrom; });
            if (bfrom == null)
                return;
            Block bto = blockList.Find(delegate(Block block) { return block.Name == NameTo; });
            if (bto == null)
                return;
            //если отсоединяем ресурс от процедуры
            if (bfrom is Resource && bto is Simulator.Action)
            {
                (bto as Action).DisconnectResource(bfrom as Resource);
            }
            else //разрываем связь по сущностям
            {
                if (bfrom is Source || bfrom is Simulator.Action || bfrom is Collector || bfrom is Sync)
                {
                    if(bfrom.Next == bto)
                        bfrom.Next = null;
                }
                //Если блок From - решение, то параметр определяет направление порта
                if (bfrom is Decision)
                {
                    for (int i = 0; i < 3; i++)
                        if ((bfrom as Decision).Next[i] == bto)
                        {
                            (bfrom as Decision).Next[i] = null;
                            break;
                        }
                }
                //если блок From - параллельность
                if (bfrom is Parall)
                {
                    if ((bfrom as Parall).Next == bto)
                        (bfrom as Parall).Next = null;
                    else if ((bfrom as Parall).Next2 == bto)
                        (bfrom as Parall).Next2 = null;
                }
                //направление next -> prev
                if ((bto is Sink || bto is Simulator.Action || bto is Decision || bto is Parall)
                    &&(bto.Prev == bfrom))
                    bto.Prev = null;
                if (bto is Collector)
                {
                    for (int i = 0; i < 3; i++)
                        if ((bto as Collector).Prev[i] == bfrom)
                        {
                            (bto as Collector).Prev[i] = null;
                            break;
                        }
                }
                if (bto is Sync)
                {
                    if ((bto as Sync).Prev == bfrom)
                        (bto as Sync).Prev = null;
                    else if ((bto as Sync).Prev2 == bfrom)
                        (bto as Sync).Prev2 = null;
                }
                
            }
        }
        /// <summary>
        /// Ликвидировать все связи между блоками
        /// </summary>
        public void DisconnectAllBlocks()
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Изменить текст блока
        /// </summary>
        /// <param name="BlockName">Имя блока</param>
        /// <param name="NewText">Новая текстовая строка блока</param>
        public void EditBlockText(string BlockName, string NewText)
        {
            Block b = blockList.Find(delegate(Block block) { return block.Name == BlockName; });
            if(b != null)
                b.Text = NewText;
        }
        /// <summary>
        /// Скопировать блоки в буфер обмена
        /// </summary>
        /// <param name="BlockNames">Имена блоков, подлежащих копированию</param>
        /// <param name="CopiedBlockNames">Новые имена блоков, которые станут копиями</param>
        public void ToClipborad(string[] BlockNames, string[] CopiedBlockNames)
        {
            DataObject data = new DataObject();
            List<Block> copies = new List<Block>();
            int i = 0;
            foreach (string name in BlockNames)
            {
                //найдем блок по имени
                Block b = blockList.Find(delegate(Block block) { return block.Name == name; });
                //клонируем его и подправим поля
                Block bc = b.Clone();
                bc.Next = bc.Prev = null;
                bc.Name = CopiedBlockNames[i];
                //добавим в список копий
                copies.Add(bc);
                i++;
            }
            data.SetData("CopiedBlockNames", copies.ToArray());
            Clipboard.SetDataObject(data);
        }
        /// <summary>
        /// Получить данные из буфера обмена
        /// </summary>
        public void FromClipborad()
        {
            DataObject data = Clipboard.GetDataObject() as DataObject;
            //если объекты типа "Блок" присутствуют в буфере
            if (data.GetDataPresent("CopiedBlockNames"))
                //поместим их в список блоков
                blockList.AddRange(data.GetData("CopiedBlockNames") as Block[]);
        }

        private void SetSinkSource(ref SimModel Sim)
        {
            //Найдем источник
            Block src = blockList.Find(delegate(Block block) { return block.GetType().Name == "Source"; });
            if (src == null)
            {
                MessageBox.Show("На диаграмме отсутствует блок \"Источник\"", "Ошибка!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            //найдем сток
            Block snk = blockList.Find(delegate(Block block) { return block.GetType().Name == "Sink"; });
            if (snk == null)
            {
                MessageBox.Show("На диаграмме отсутствует блок \"Сток\"", "Ошибка!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            //присвоим их модели
            Sim.Source = src as Source;
            Sim.Sink = snk as Sink;
        }

        /// <summary>
        /// Показать окно имитационного моделирования.
        /// Не запускайте эту функцию, пока не настроены все связи в списке блоков!
        /// </summary>
       
        public void ShowSimulationWindow()
        {
            SimModel sim2 = sim.Clone(); //копируем модель для запуска. Это ОБЯЗАТЕЛЬНО!!!

            List<string> catNames = PrepareModel(ref sim2);
            //создадим окно и настроим его
            FormSimulation fs = new FormSimulation();
            fs.Sim = sim2;
            fs.Res = sim2.Resources;//создадим в копии списка ресурсов
            fs.Categories = catNames;

            //покажем
            fs.ShowDialog();
        }

         /// <summary>
        /// Показать окно оптимизации.
        /// Не запускайте эту функцию, пока не настроены все связи в списке блоков!
        /// </summary>

        public void ShowOptimizationWindow()
        {

            FormOptimizer fo = new FormOptimizer();
            fo.ParamNames = paramNames;

            SimModel simClone = Sim.Clone();

            if (simClone.BlockList.Count == 0)
            {
                PrepareModel(ref simClone);//подготовим модель Sim к запуску
            }


            opt.Categories = categories;
            opt.StartModel = simClone;
           
            //если оптимизатор еще не вызывался, то надо добавить списки 0 
            //в веса и ограничения параметров
            if (opt.ParamWeights.Count == 0)
            {
                opt.ParamWeights.Add(0);
                opt.ParamConstraints.Add(0);
                for (int i = 0; i < paramNames.Count; i++)
                {
                    opt.ParamWeights.Add(0);
                    opt.ParamConstraints.Add(0);
                }
            }

            fo.Optimizer = opt;
            if (fo.ShowDialog() == DialogResult.OK)
            {//если пользователь нажал ОК, то присвоим ярду лучшую модель
                Sim = opt.BestSim;
                Sim.Res.Clear();//очистим список ресурсов, по которым идет сбор статистики
            }
            opt = fo.Optimizer;//настроенный оптимизатор вернем в ядро
        }

        /// <summary>
        /// Запомнить текущее состояние диаграммы
        /// </summary>
        public void Memorize()
        {
            //скопируем список блоков
            Block b2;
            List<Block> copied = new List<Block>();
            foreach(Block b in blockList)
            {
                b2 = b.Clone();
                copied.Add(b2);
            }
            //запомним
            if (mementoList.Count > currentState + 1)//если делали откат назад
                mementoList.RemoveRange(currentState + 1, mementoList.Count - currentState - 1);//удалим все, что было прежде
            mementoList.Add(copied);

            currentState++;
        }

        /// <summary>
        /// Откатить диаграмму на состояние назад
        /// </summary>
        public void Undo()
        {
            if (currentState == 0)
                return;
            currentState--;
            //скопируем список номер currentState
            Block b2;
            List<Block> alternative = new List<Block>();
            foreach (Block b in mementoList[currentState])
            {
                b2 = b.Clone();
                alternative.Add(b2);
            }

            //присвоим текущему списку фигур это значение
            blockList = alternative;
        }

        /// <summary>
        /// Откатить диаграмму на состояние вперед
        /// </summary>
        public void Redo()
        {
            if(currentState < mementoList.Count - 1)
                currentState++;
            //скопируем список номер currentState
            Block b2;
            List<Block> alternative = new List<Block>();
            foreach (Block b in mementoList[currentState])
            {
                b2 = b.Clone();
                alternative.Add(b2);
            }
            //присвоим текущему списку фигур это значение
            blockList = alternative;
        }

        /// <summary>
        /// Сохранить модель
        /// </summary>
        /// <param name="fs">Файловый поток, в который сохраняется визуальная часть диаграммы</param>
        public void Save(FileStream fs)
        {
            BinaryFormatter BF = new BinaryFormatter();
            //сериализуем все блоки
            BF.Serialize(fs, blockList);
            //сериализуем названия параметров
            BF.Serialize(fs, paramNames);
            //сериализуем параметры времени модели
            BF.Serialize(fs, Sim.SimulationTime);
            BF.Serialize(fs, Sim.SimulationTimeUnits);
        }

        /// <summary>
        /// Открыть модель
        /// </summary>
        public void Open(FileStream fs)
        {
            //FileStream fs = new FileStream(filename, FileMode.Open);
            BinaryFormatter BF = new BinaryFormatter();
            //deсериализуем все блоки
            blockList = BF.Deserialize(fs) as List<Block>;
            //десериализуем список параметров
            paramNames = BF.Deserialize(fs) as List<string>;
            //deсериализуем важные параметры модели
            Sim.SimulationTime = (Decimal)BF.Deserialize(fs);
            Sim.SimulationTimeUnits = (TimeUnits)BF.Deserialize(fs);
        }

        /// <summary>
        /// Функция вызывается, когда текст на блоке был изменен (например, из графического интерфейса)
        /// </summary>
        /// <param name="BlockName"></param>
        /// <param name="NewText"></param>
        public void ChangeText(string BlockName, string NewText)
        {
            Block b = blockList.Find(block => block.Name == BlockName);
            if(b!=null)
                b.Text = NewText;
        }
        /// <summary>
        /// Удаление блока и всех его связей с другими блоками
        /// </summary>
        /// <param name="BlockName"></param>
        public void RemoveBlock(string BlockName)
        {
            //проверяем всевозможные типы подключения и удаляем их, если возможно
            foreach (Block b in blockList)
            {
                DisconnectBlocks(b.Name, BlockName);
                DisconnectBlocks(BlockName, b.Name);
            }
            //удаляем блок
            blockList.Remove(blockList.Find(b => b.Name == BlockName));
        }

        /// <summary>
        /// Подготовить подель к запуску
        /// </summary>
        /// <returns>Список названий категорий, по которым можно собирать статистику</returns>
        private List<string> PrepareModel(ref SimModel simM)
        {

            simM.BlockList.Clear();
            simM.BlockList = blockList;//настроим список всех блоков
            //настроим сток, исток для ИМ
            SetSinkSource(ref simM);

            //сделаем список ресурсов и список категорий
            List<Resource> res = new List<Resource>();
            List<string> catNames = new List<string>();
            foreach (Block b in simM.BlockList)
            {
                if (b.GetType().Name == "Resource")
                {
                    res.Add(b as Resource);
                    //просмотрим атомарные ресурсы и занесем в список catNames все их категории
                    foreach (AtomicRes ar in (b as Resource).Atoms)
                    {
                        if (!catNames.Contains(ar.Category))
                            catNames.Add(ar.Category);
                    }
                }
            }
            if(simM.Resources.Count == 0)
                simM.Resources = res;
            return catNames;
        }

        /// <summary>
        /// Показать окно настройки параметров
        /// </summary>
        public void ShowSetParamsWindow()
        {
            FormParams fp = new FormParams();
            fp.ParamNames = paramNames;
            if (fp.ShowDialog() == DialogResult.OK)
            {
                paramNames = fp.ParamNames;
                int i = 0;
                foreach (string s in paramNames)
                {
                    //если параметр стал несущественным, обнулим все, что с ним связано
                    if (s == "")
                    {
                        //у всех блоков Action сделаем вес этого параметра нулевым
                        foreach (Block b in blockList)
                        {
                            switch (b.GetType().Name)
                            {
                                case "Action":
                                    (b as Action).WeightsP[i] = 0;
                                    break;
                            }
                        }

                        if (opt.ParamConstraints.Count > 0)
                        {//если оптимизатор настроен
                            opt.ParamConstraints[i] = 0;
                            opt.ParamWeights[i] = 0;
                        }
                    }
                    i++;
                }
            }
        }
    }
}
